package com.keyin.android.view;

import java.util.ArrayList;
import java.util.HashMap;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
import android.widget.Scroller;

public class PageControl extends ViewGroup {

	private boolean allowLongPress;

	private int state;

	private int childIndex;
	private int nextIndex;

	private float lastMotionX;
	private VelocityTracker velocityTracker;
	private int mTouchSlop;
	private int mMaximumVelocity;

	private int scrollX;
	private int scrollY;

	private Scroller mScroller;

	private HashMap<View, ArrayList<Rect>> cannotInterceptRect;

	private PageControlNavigation navigation;

	private int deltaX;

	public PageControl(Context context) {
		super(context);

		init();
	}

	public PageControl(Context context, AttributeSet attrs) {
		super(context, attrs);

		init();
	}

	private void init() {
		final ViewConfiguration configuration = ViewConfiguration
				.get(getContext());
		mTouchSlop = configuration.getScaledTouchSlop();
		mMaximumVelocity = 1000;

		mScroller = new Scroller(getContext(), (AnimationUtils.loadInterpolator(getContext(),
                android.R.anim.overshoot_interpolator)));
	}

	@Override
	public void removeAllViews() {
		super.removeAllViews();

		init();
		state = STATE_REST;
		scrollX = 0;
		scrollY = 0;

		childIndex = 0;
		nextIndex = 0;
	}

	public void setNavigation(PageControlNavigation navigation) {
		this.navigation = navigation;
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childLeft = 0;

		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			try {
				if (child.getVisibility() != View.GONE) {
					final int childWidth = child.getMeasuredWidth();
					child.layout(childLeft, 0, childLeft + childWidth,
							child.getMeasuredHeight());
					childLeft += childWidth;

				}
			} catch (Exception e) {
				// Log.e("=========", i + " " + child.getId() + " ", e);
			}
		}
	}

	private boolean canIntercept(int x, int y) {
		View child = getChildAt(childIndex);
		if (cannotInterceptRect == null || cannotInterceptRect.isEmpty()) {
			return false;
		}
		ArrayList<Rect> rects = cannotInterceptRect.get(child);
		if (rects == null || rects.isEmpty()) {
			return false;
		}
		boolean contains = false;
		for (int i = 0, size = rects.size(); i < size; i++) {
			contains = rects.get(i).contains(x, y);
			if (contains) {
				break;
			}
		}
		return contains;
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// Log.i("onInterceptTouchEvent", ev.getAction() + " | " + ev.getX() +
		// " "
		// + ev.getY() + " " + state);

		int action = ev.getAction();
		int x = (int) ev.getX();
		int y = (int) ev.getY();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			if (canIntercept(x, y)) {
				return false;
			}
			// Remember location of down touch
			lastMotionX = x;
			allowLongPress = true;

			/*
			 * If being flinged and user touches the screen, initiate drag;
			 * otherwise don't. mScroller.isFinished should be false when being
			 * flinged.
			 */
			state = mScroller.isFinished() ? STATE_REST : STATE_DRAGGING;
			break;
		case MotionEvent.ACTION_MOVE:
			if (canIntercept((int) x, (int) y)) {
				return false;
			}

			setDraggingState(x);
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			clearChildrenCache();
			break;
		}
		return state != STATE_REST;
	}

	public boolean isScrolling() {
		return !mScroller.isFinished();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// Log.i("onTouchEvent", event.getAction() + " | " + event.getX() + " "
		// + event.getY() + " " + state);
		if (!mScroller.isFinished()) {
			return false;
		}

		int action = event.getAction();
		if (velocityTracker == null) {
			velocityTracker = VelocityTracker.obtain();
		}
		velocityTracker.addMovement(event);

		float x = event.getX();
		float y = event.getY();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			if (canIntercept((int) x, (int) y)) {
				return false;
			}
			lastMotionX = x;
			break;
		case MotionEvent.ACTION_MOVE:
			setDraggingState((int) x);
			if (state == STATE_DRAGGING) {

				deltaX = (int) (lastMotionX - x);

				lastMotionX = x;
				if (deltaX < 0) {
					if (scrollX > 0) {
						int dx = Math.max(-scrollX, deltaX);
						scrollBy(dx, 0);
						scrollX = scrollX + dx;
						postInvalidate();
					} 
				} else if (deltaX > 0) {
					final int availableToScroll = getChildAt(
							getChildCount() - 1).getRight()
							- scrollX - getWidth();
					if (availableToScroll > 0) {
						int dx = Math.min(availableToScroll, deltaX);
						scrollBy(dx, 0);
						scrollX = scrollX + dx;
						postInvalidate();
					}
				}
			}
			break;

		case MotionEvent.ACTION_CANCEL:
			state = STATE_REST;
			break;
		case MotionEvent.ACTION_UP:
			if (state == STATE_DRAGGING) {
				final VelocityTracker vt = velocityTracker;
				vt.computeCurrentVelocity(mMaximumVelocity);

				int velocityX = (int) vt.getXVelocity();

				if (velocityX > SNAP_VELOCITY
						&& Math.abs(deltaX) > MOVE_DISTANCE && childIndex > 0) {
					// Fling hard enough to move left
					snapToScreen(childIndex - 1);
				} else if (velocityX < -SNAP_VELOCITY
						&& Math.abs(deltaX) > MOVE_DISTANCE
						&& childIndex < getChildCount() - 1) {
					// Fling hard enough to move right
					snapToScreen(childIndex + 1);
				} else {
					snapToDestination();
				}
				clearChildrenCache();
			}
			clearVelcityTracker();
			allowLongPress = false;
			state = STATE_REST;
			break;
		}
		return true;
	}

	private void clearVelcityTracker() {
		if (velocityTracker != null) {
			velocityTracker.recycle();
			velocityTracker = null;
		}
	}

	private void setDraggingState(int x) {
		final int xDiff = (int) Math.abs(x - lastMotionX);
		final int touchSlop = mTouchSlop;
		boolean xMoved = xDiff > touchSlop;

		if (xMoved) {
			// Scroll if the user moved far enough along the X axis
			state = STATE_DRAGGING;
			enableChildrenCache();
			// Either way, cancel any pending longpress
			if (allowLongPress) {
				allowLongPress = false;
				// Try canceling the long press. It could also have been
				// scheduled
				// by a distant descendant, so use the mAllowLongPress flag
				// to block
				// everything
				final View currentScreen = getChildAt(childIndex);
				currentScreen.cancelLongPress();
			}
		}

//		 Logger.d("xDiff: " + xDiff
//		 + " touchSlop : " + touchSlop + " xMoved: " + xMoved + " state:" + state);
	}

	public boolean show(int index) {
		childIndex = index;
		scrollX = childIndex * getWidth();

		snapToScreen(index);
		return true;
	}
	
	public void setFocus(int index){
		if (!mScroller.isFinished()) {
			return ;
		}
		childIndex = index;
	}

	void snapToScreen(int whichScreen) {
		snapToScreen(whichScreen, true);
	}

	void snapToScreen(int whichScreen, boolean notify) {
		if (!mScroller.isFinished()) {
			return;
		}

		whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
		boolean changingScreens = whichScreen != childIndex;

		nextIndex = whichScreen;

		// Log.d("snapToScreen Start : ", "whichScreen: " + whichScreen
		// + " childIndex : " + childIndex + " nextIndex: " + nextIndex);

		View focusedChild = getFocusedChild();
		if (focusedChild != null && changingScreens
				&& focusedChild == getChildAt(childIndex)) {
			focusedChild.clearFocus();
		}

		if (notify && changingScreens && navigation != null) {
			navigation.onChange(nextIndex, childIndex, getChildCount());
		}

		int startX = scrollX;
		int newX = 0;
		int delta = 0;

		newX = whichScreen * getWidth();
		delta = newX - startX;

		// Log.d("snapToScreen : ", "delta: " + delta + " newX: " + newX
		// + " scrollX: " + scrollX);
		mScroller.startScroll(startX, 0, delta, 0, Math.abs(delta) * 2);
		invalidate();
	}

	private void snapToDestination() {
		final int screenWidth = getWidth();
		final int whichScreen = (scrollX + (screenWidth / 2)) / screenWidth;

		snapToScreen(whichScreen);
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		boolean fastDraw = state != STATE_DRAGGING
				&& nextIndex == INVALID_SCREEN;
		// If we are not scrolling or flinging, draw only the current screen
		if (fastDraw) {
			drawChild(canvas, getChildAt(childIndex), getDrawingTime());
		} else {
			final long drawingTime = getDrawingTime();
			// If we are flinging, draw only the current screen and the target
			// screen
//			Logger.w("-nextIndex:" + nextIndex + "-childIndex:" + childIndex
//					+ "-getChildCount" + getChildCount());
			if (nextIndex >= 0 && nextIndex < getChildCount()
					&& Math.abs(childIndex - nextIndex) == 1) {
				drawChild(canvas, getChildAt(childIndex), drawingTime);
				drawChild(canvas, getChildAt(nextIndex), drawingTime);
			} else {
				// If we are scrolling, draw all of our children
				final int count = getChildCount();
				for (int i = 0; i < count; i++) {
					drawChild(canvas, getChildAt(i), drawingTime);
				}
			}
		}

		if (navigation != null) {
			int left = scrollX;
			navigation.onDraw(canvas, new Rect(left, 0, left + getWidth(),
					getHeight()), getChildCount(), childIndex);
		}
	}

	void enableChildrenCache() {
		setChildrenDrawnWithCacheEnabled(true);
	}

	void clearChildrenCache() {
		setChildrenDrawnWithCacheEnabled(false);
	}

	@Override
	public boolean dispatchUnhandledMove(View focused, int direction) {
		return super.dispatchUnhandledMove(focused, direction);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		// The children are given the same width and height as the workspace
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}
	}

	/**
	 * Add an cannot be intercept rect.
	 * 
	 * @param child
	 * @param rect
	 */
	public void addCannotInterceptTouchEventChildRect(View child, Rect rect) {
		int count = getChildCount();
		boolean add = false;
		for (int i = 0; i < count; i++) {

			View thisClassChild = getChildAt(i);

			if (thisClassChild == child || isChild(thisClassChild, child)) {
				if (cannotInterceptRect == null) {
					cannotInterceptRect = new HashMap<View, ArrayList<Rect>>();
				}
				ArrayList<Rect> rects = cannotInterceptRect.get(thisClassChild);
				if (rects == null) {
					rects = new ArrayList<Rect>();
				}
				rects.add(rect);
				cannotInterceptRect.put(getChildAt(i), rects);
				add = true;
				break;
			}
		}
		if (!add) {
			throw new IllegalArgumentException(
					"Cannot find the argumnet child in children!");
		}
	}

	private boolean isChild(View parent, View child) {
		if (!(parent instanceof ViewParent)) {
			return false;
		}
		ViewParent p = child.getParent();
		while (p != null) {
			if (p == parent) {
				return true;
			}
			p = p.getParent();
		}
		return false;
	}

	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			scrollX = mScroller.getCurrX();
			scrollY = mScroller.getCurrY();
			
			scrollTo(scrollX, scrollY);
			postInvalidate();
		} else if (nextIndex != INVALID_SCREEN) {
			childIndex = Math.max(0, Math.min(nextIndex, getChildCount() - 1));
			nextIndex = INVALID_SCREEN;
			clearChildrenCache();
		}
	}

	@Override
	protected boolean onRequestFocusInDescendants(int direction,
			Rect previouslyFocusedRect) {

		int focusableScreen;
		if (nextIndex != INVALID_SCREEN) {
			focusableScreen = nextIndex;
		} else {
			focusableScreen = childIndex;
		}
		getChildAt(focusableScreen).requestFocus(direction,
				previouslyFocusedRect);
		return false;
	}

	public int getFocusIndex() {
		return childIndex;
	}

	public View getFocusView() {
		return getChildAt(getFocusIndex());
	}

	private static final int STATE_REST = 0;
	private static final int STATE_DRAGGING = 1;
	/**
	 * The velocity at which a fling gesture will cause us to snap to the next
	 * screen
	 */
	private static final int SNAP_VELOCITY = 500;
	private static final int MOVE_DISTANCE = 50;
	private static final int INVALID_SCREEN = -1;
}
